﻿using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using MyCompany.MyProject.Common;
using MyCompany.MyProject.Common.HttpContextUser;
using MyCompany.MyProject.IService;
using MyCompany.MyProject.Model;
using MyCompany.MyProject.Model.DTO;
using MyCompany.MyProject.Model.Models;
using MyCompany.MyProject.Service;
using System.Security.Claims;
using System.Text.RegularExpressions;

#nullable disable

namespace MyCompany.MyProject.Extensions.Auth;

public class PermissionRequirementHandler : AuthorizationHandler<PermissionRequirement>, IAuthorizationRequirement
{
    private readonly IHttpContextAccessor accessor;
    private readonly IUserService userService;
    private readonly IUser _user;

    public IAuthenticationSchemeProvider Schemes { get; set; }

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="schemes"></param>
    /// <param name="accessor"></param>
    /// <param name="userService"></param>
    public PermissionRequirementHandler(IAuthenticationSchemeProvider schemes, IHttpContextAccessor accessor, IUserService userService,IUser user )
    {
        Schemes = schemes;
        this.accessor = accessor;
        this.userService = userService;
        _user = user;
    }

    public List<PermissionItem> Permissions { get; set; }
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
    {
        var httpContext = accessor.HttpContext;

        if (!requirement.Permissions.Any())
        {
            var data = await userService.RoleModuleMaps();
            var list = new List<PermissionItem>();
            list = (from item in data
                    where item.IsDeleted == false
                    orderby item.Id
                    select new PermissionItem
                    {
                        Url = item.Modules?.LinkUrl,
                        Role = item.Role?.Name.ObjToString(),
                    }).ToList();
            requirement.Permissions = list;
        }

        if(httpContext!=null)
        {
            var questUrl = httpContext.Request.Path.Value.ToLower();

            // 整体结构类似认证中间件UseAuthentication的逻辑，具体查看开源地址
            // https://github.com/dotnet/aspnetcore/blob/master/src/Security/Authentication/Core/src/AuthenticationMiddleware.cs
            httpContext.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
            {
                OriginalPath = httpContext.Request.Path,
                OriginalPathBase = httpContext.Request.PathBase,
            });

            // Give any IAuthenticationRequestHandler schemes a chance to handle the request
            // 主要作用是: 判断当前是否需要进行远程验证，如果是就进行远程验证
            var handlers = httpContext.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
            foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
            {
                if (await handlers.GetHandlerAsync(httpContext, scheme.Name) is IAuthenticationRequestHandler
                        handler && await handler.HandleRequestAsync())
                {
                    context.Fail();
                    return;
                }
            }

            //判断请求是否拥有凭据，既有没有登录
            var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
            if(defaultAuthenticate != null)
            {
                var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name);

                // 是否开启测试环境
                var isTestCurrent = AppSettings.app("AppSettings", "UseLoadTest").ObjToBool();
                // result?.Principal 不为空既登录成功
                if (result?.Principal != null || isTestCurrent)
                {
                    if (!isTestCurrent) httpContext.User = result.Principal;
                    // 先校验用户的数据，再校验菜单权限相关
                    var userInfo = new SysUserInfo();
                   // if (!Permissions.IsUseIds4)
                    {
                        //校验用户
                        userInfo = await userService.QueryById(_user.ID, true);
                        if (userInfo == null)
                        {
                            _user.MessageModel = new ApiResponse(StatusCode.CODE401, "用户不存在或已被删除").MessageModel;
                            context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg));
                            return;
                        }

                        if (userInfo?.IsDeleted== true)
                        {
                            _user.MessageModel = new ApiResponse(StatusCode.CODE401, "用户已被删除,禁止登录!").MessageModel;
                            context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg));
                            return;
                        }

                        if (!userInfo.Enable)
                        {
                            _user.MessageModel = new ApiResponse(StatusCode.CODE401, "用户已被禁用!禁止登录!").MessageModel;
                            context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg));
                            return;
                        }
                    }

                    // 判断 token 是否过期，过期则重新登录
                    var isExp = false;
                    var claimsValue = httpContext.User.Claims.FirstOrDefault(s=>s.Type==ClaimTypes.Expiration).Value;
                    // jwt
                    //isExp = claimsValue != null && DateTime.Parse(claimsValue)>DateTime.Now;
                    isExp =
                           (httpContext.User.Claims.FirstOrDefault(s => s.Type == ClaimTypes.Expiration)
                               ?.Value) != null &&
                           DateTime.Parse(httpContext.User.Claims
                               .FirstOrDefault(s => s.Type == ClaimTypes.Expiration)?.Value) >= DateTime.Now;
                    if (!isExp)
                    {
                        context.Fail(new AuthorizationFailureReason(this, "授权已过期,请重新授权"));
                        return;
                    }

                    // 获取当前用户的角色信息
                    var currentUserRoles = new List<string>();
                    currentUserRoles = (from item in httpContext.User.Claims
                                        where item.Type==ClaimTypes.Role
                                        select item.Value).ToList();
                    if(!currentUserRoles.Any() )
                    {
                        currentUserRoles = ( from item in httpContext.User.Claims
                                             where item.Type=="role"
                                             select item.Value).ToList();
                    }

                    // 超级管理员 默认拥有所有的权限
                    if (currentUserRoles.All(s => s != "SuperAdmin"))
                    {
                        var isMatchRole = false;
                        var permissionRoles = requirement.Permissions.Where(w=>currentUserRoles.Contains(w.Role));
                        foreach(var item in permissionRoles)
                        {
                            try
                            {
                                if (Regex.Match(questUrl, item.Url?.ObjToString().ToLower())?.Value == questUrl)
                                {
                                    isMatchRole = true;
                                    break;
                                }
                            }
                            catch (Exception)
                            {

                            }
                        }
                        // 验证权限
                        if (currentUserRoles.Count <= 0 || !isMatchRole)
                        {
                            context.Fail();
                            return;
                        }
                    }
                    context.Succeed(requirement);
                    return;
                }
            }

            if ((!httpContext.Request.Method.Equals("POST") || !httpContext.Request.HasFormContentType))
            {
                context.Fail();
                return;
            }
        }
    }
}
